﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Media;
using MyFloridaHouse.libSvCommonUtilities;
using MyFloridaHouse.libSvOpenDistricts.Census;
using MyFloridaHouse.libSvOpenDistricts.DataService;
using MyFloridaHouse.libSvOpenDistricts.Dialogs;
using MyFloridaHouse.libSvUtilities;
using MyFloridaHouse.ServiceConn.DistrBldrSvcRef;
using System.IO;


namespace MyFloridaHouse.libSvOpenDistricts.Districts
{
    [DataContract(IsReference=true)]
    [KnownType(typeof(CensusGeometryNode))]
    
    public partial class DistrictPlan : INotifyPropertyChanged
    {

        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion


        private bool _isCensusGeometryInitializeCompleted = false;
        public bool isCensusGeometryInitializeCompleted
        {
            get { return _isCensusGeometryInitializeCompleted; }
            set
            {
                if (_isCensusGeometryInitializeCompleted != value)
                {
                    _isCensusGeometryInitializeCompleted = value;
                    if (value)
                    {
                        NotifyPropertyChanged("isCensusGeometryInitializeCompleted");
                    }
                }
            }
        }

        private bool _isShowDistrictRibbonSelected = true;
        public bool isShowDistrictRibbonSelected
        {
            get { return _isShowDistrictRibbonSelected; }
            set 
            { 
                if (_isShowDistrictRibbonSelected != value) 
                { 
                    _isShowDistrictRibbonSelected = value;
                    NotifyPropertyChanged("isShowDistrictRibbonSelected");
                    NotifyPropertyChanged("isDistrictRibbonVisible");
                } 
            }
        }

        private bool _isShowDistrictStragglerRibbonSelected = false;
        public bool isShowDistrictStragglerRibbonSelected
        {
            get { return _isShowDistrictStragglerRibbonSelected; }
            set 
            { 
                if (_isShowDistrictStragglerRibbonSelected != value) 
                { 
                    _isShowDistrictStragglerRibbonSelected = value;
                    NotifyPropertyChanged("isShowDistrictStragglerRibbonSelected");
                    NotifyPropertyChanged("isDistrictStragglerRibbonVisible");
                    NotifyPropertyChanged("isDistrictExtentVisible");
                } 
            }
        }

        public bool isDistrictRibbonVisible
        {
            get { return ((districtCount > 0) & isShowDistrictRibbonSelected); }
        }

        public bool isDistrictStragglerRibbonVisible
        {
            get { return (isShowDistrictStragglerRibbonSelected); }
        }

        public bool isDistrictExtentVisible
        {
            get {
                if (selectedDistrictSubShapeList == null)
                {
                    return false;
                }
                else
                {
                    return ((selectedDistrictSubShapeList.Count() > 0));
                }
            }
        }

        private string _districtPlanName = "District Plan Name";
        public string districtPlanName
        {
            get { return _districtPlanName; }
            set
            {
                if (_districtPlanName != value)
                {
                    _districtPlanName = value;
                    NotifyPropertyChanged("districtPlanName");
                }
            }
        }

        private string _districtPlanDescription = "District Plan Description";
        public string districtPlanDescription
        {
            get { return _districtPlanDescription; }
            set
            {
                if (_districtPlanDescription != value)
                {
                    _districtPlanDescription = value;
                    NotifyPropertyChanged("districtPlanDescription");
                }
            }
        }

        private string _districtPlanTypeName = "";
        public string districtPlanTypeName
        {
            get { return _districtPlanTypeName; }
            set
            {
                if (_districtPlanTypeName != value)
                {
                    _districtPlanTypeName = value;
                    NotifyPropertyChanged("districtPlanTypeName");
                }
            }
        }

        private int _districtCount;
        public int districtCount
        {
            get { return _districtCount; }
            set 
            { 
                if (_districtCount != value) 
                { 
                    _districtCount = value;
                    NotifyPropertyChanged("districtCount");
                    NotifyPropertyChanged("isDistrictRibbonVisible");
                    NotifyPropertyChanged("isDistrictStragglerRibbonVisible");
                    NotifyPropertyChanged("isDistrictExtentVisible");
                    NotifyPropertyChanged("selectedDistrictSubShapeList");
                    NotifyPropertyChanged("selectedDistrictHoleList");
                } 
            }
        }

        private ObservableCollection<District> _districtList = new ObservableCollection<District>();
        public ObservableCollection<District> districtList
        {
            get { return _districtList; }
            set
            {
                if (_districtList != value)
                {
                    _districtList = value;
                    NotifyPropertyChanged("districtList");
                    NotifyPropertyChanged("selectedDistrictSubShapeList");
                }
            }
        }

        //
        private ObservableCollection<DistrictSubShapeMetadata> _selectedDistrictSubShapeList = new ObservableCollection<DistrictSubShapeMetadata>();
        public ObservableCollection<DistrictSubShapeMetadata> selectedDistrictSubShapeList
        {
            get
            {
                return _selectedDistrictSubShapeList;
            }
            set
            {
                _selectedDistrictSubShapeList = value;
                NotifyPropertyChanged("selectedDistrictSubShapeList");
                NotifyPropertyChanged("isDistrictExtentVisible");
            }
        }

        private ObservableCollection<DistrictSubShapeMetadata> _selectedDistrictHoleList = new ObservableCollection<DistrictSubShapeMetadata>();
        public ObservableCollection<DistrictSubShapeMetadata> selectedDistrictHoleList
        {
            get
            {
                return _selectedDistrictHoleList;
            }
            set
            {
                _selectedDistrictHoleList = value;
                NotifyPropertyChanged("selectedDistrictHoleList");
            }
        }

        public BoundingBoxDTO selectedDistrictBoundingBox
        {
            get { return districtList[selectedDistrict].boundingBox; }
        }



        private int _districtSelectedIndex = -1;
        public int districtSelectedIndex
        {
            get { return _districtSelectedIndex; }
            set
            {
                if (_districtSelectedIndex != value)
                {
                    _districtSelectedIndex = value;
                    if (districtSelectedIndex > -1)
                    {
                        foreach (District dist in districtList)
                        {
                            dist.isSelected = false;
                        }
                        districtList[districtSelectedIndex].isSelected = true;
                    }

                    UpdateSelectedDistrictSubShapeList();

                    NotifyPropertyChanged("districtSelectedIndex");
                    NotifyPropertyChanged("selectedDistrict");
                    NotifyPropertyChanged("isDistrictRibbonVisible");
                    NotifyPropertyChanged("districtSelectedFillColorBrush");
                    NotifyPropertyChanged("isDistrictStragglerRibbonVisible");
                    NotifyPropertyChanged("isDistrictExtentVisible");
                }
            }
        }

        public SolidColorBrush districtSelectedFillColorBrush
        {
            get
            {
                if ((districtSelectedIndex >= 0) & (districtSelectedIndex < districtList.Count()))
                {
                    return DistrictColors.GetBrushFromIndex(districtList[districtSelectedIndex].districtID);
                }
                else
                {
                    return new SolidColorBrush(Colors.Transparent);
                }
            }
        }

        // indicates what district would be affected if a district action is taken
        public int selectedDistrict
        {
            get
            {
                if (districtList == null)
                {
                    return -1;
                }
                if ((districtSelectedIndex >= 0) & (districtSelectedIndex <= districtList.Count))
                { return districtList[districtSelectedIndex].districtID; }
                else
                { return -1; }
            }
            set
            { // should throw an error here
                MessageBox.Show("Can't set selected district, should be read only");
            }
        }

        // indicates what district is being pointed at
        private string _currentDistrict = "Unassigned";
        public string currentDistrict
        {
            get { return _currentDistrict; }
            set
            {
                if (_currentDistrict != value)
                {
                    _currentDistrict = value;
                    NotifyPropertyChanged("currentDistrict");
                }
            }
        }





        private int _districtPlanTargetPopulation = Constants.TOTAL_POPULATION_TO_BE_APPORTIONED;
        public int districtPlanTargetPopulation
        {
            get { return _districtPlanTargetPopulation; }
            set
            {
                if (_districtPlanTargetPopulation != value)
                {
                    _districtPlanTargetPopulation = value;

                    NotifyPropertyChanged("districtPlanTargetPopulation");
                }
            }
        }

        private bool _districtLockAllState = false;
        public bool districtLockAllState
        {
            get { return _districtLockAllState; }
            set
            {
                if (_districtLockAllState != value)
                {
                    _districtLockAllState = value;

                    NotifyPropertyChanged("districtLockAllState");
                }
            }
        }
        


        private DistrictColors districtCustomColors;
        //        private DistrictColors districtCustomColors = new DistrictColors("WEBSAFE");

        public DistrictPlan_XML xmlDistrictPlan {get; set;}

        private DistrictPlanTypes districtPlanType;

        public string districtPlanTypeID = "";



        private DistrictClipboard _districtClipboard = new DistrictClipboard();
        private DistrictClipboard districtClipboard { get; set; }

        private List<int> _changedDistrictsList = new List<int>();
        public List<int> changedDistrictsList
        {
            get { return _changedDistrictsList; }
            set { _changedDistrictsList = value; }
        }

        public bool isUndoAvailable
        {
            get
            {
                return censusGeometry.history.isUndoAvailable;
            }
        }
        
        private CensusGeometry censusGeometry = null;

        public DistrictPlan(CensusGeometry censusGeometry)
        {
            this.censusGeometry = censusGeometry;
        }


        public void InitializeDistrictPlan(string newDistrictPlanName, string newPlanDescription, int newDistrictCount, string newDistrictPlanTypeID, string newDistrictPlanTypeName)
        {
            //todo: Reset the snapshot file when we start a new district plan

            //TODO: need to initialize district plan with target population for each district?
            int districtTargetPopulation = Constants.TOTAL_POPULATION_TO_BE_APPORTIONED / newDistrictCount;

            // reset 
            districtCount = newDistrictCount;
            districtPlanName = newDistrictPlanName;
            districtPlanDescription = newPlanDescription;
            districtPlanTypeID = newDistrictPlanTypeID;
            districtPlanTypeName = newDistrictPlanTypeName;

            districtCustomColors = new DistrictColors("PREDEFINED");

            censusGeometry.ResetDistrictAll();

            districtList = new ObservableCollection<District>();
            for (var ndx = 0; ndx <= newDistrictCount; ndx++)
            {
                byte[] emptyDistrict = CompressionManager.Compress("");

                District newDistrict = new District(ndx, ndx.ToString(), districtTargetPopulation);
                districtList.Add(newDistrict);
                
            }
            // indicate that district 0 is the default district for all unselected geometries
            districtList[0].displayName = "Unassigned";
            districtSelectedIndex = 1;

            selectedDistrictSubShapeList = new ObservableCollection<DistrictSubShapeMetadata>();
            selectedDistrictHoleList = new ObservableCollection<DistrictSubShapeMetadata>();
            districtClipboard = new DistrictClipboard();
            changedDistrictsList = new List<int>();

            isShowDistrictRibbonSelected = true;
        }



        void MemberPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyPropertyChanged(e.PropertyName);
        }

        public bool LoadDistrictPlanFromDOJ(StreamReader importFileStreamReader,  int districtCount )
        {
            return censusGeometry.FromDOJ(importFileStreamReader, districtCount);
        }

        public List<string> ToCondensedList(int districtCount)
        {
            return censusGeometry.ToCondensedList(districtCount);
        }

        public string GetDistrictFromRegionID(int layerID, int regionID)
        {
            return censusGeometry.GetDistrictFromRegionID(layerID, regionID);
        }

        #region clipboard helpers (Cut, Copy, Paste)

        public bool DistrictCutCanExecute()
        {
            if ((districtSelectedIndex > 0) & (districtSelectedIndex < districtList.Count()))
            {
                if (districtList[districtSelectedIndex].isLocked == false)
                {
                    return true;
                }
            }
            return false;
        }
        
        public void DistrictCut()
        {
            List<UOG> uogIDList = GetDistrictUogIDs(districtCount, districtSelectedIndex);
            MoveShapesToNewDistrict(Constants.UnassignedDistrictID, uogIDList);
            districtClipboard.SaveDistrictsToClipboard(uogIDList);
        }

        public bool DistrictCopyCanExecute()
        {
            if (districtSelectedIndex > 0)
            {
                return true;
            }
            return false;
        }

        public void DistrictCopy()
        {
            List<UOG> uogIDList = GetDistrictUogIDs(districtCount, districtSelectedIndex);
            districtClipboard.SaveDistrictsToClipboard(uogIDList);

        }

        public bool DistrictPasteCanExecute()
        {
            if ((districtSelectedIndex > 0) & (districtSelectedIndex < districtList.Count()))
            {
                if (districtList[districtSelectedIndex].isLocked == false)
                {
                    return true;
                }
            }
            return false;

        }
        public void DistrictPaste()
        {
            List<UOG> uogIDList = districtClipboard.LoadDistrictsFromClipboard();
            MoveShapesToNewDistrict(districtSelectedIndex, uogIDList);
        }


        #endregion


        public void ToggleDistrictLockAll()
        {

            districtLockAllState = !districtLockAllState;

            for (int ndx = 1; ndx < districtList.Count; ndx++)
            {
                districtList[ndx].isLocked = districtLockAllState;
            }

            censusGeometry.SetDistrictLockAll(districtLockAllState);
        }




        public void ToggleSelectedDistrictLock()
        {
            if (districtList[districtSelectedIndex].isLocked == true)
            {
                districtList[districtSelectedIndex].isLocked = false;
            }
            else
            {
                districtList[districtSelectedIndex].isLocked = true;
            }

            censusGeometry.SetLockByDistrict(selectedDistrict, districtList[districtSelectedIndex].isLocked);

        }

        public void SetDistrictTargetPopulation(int targetPopulation)
        {
            foreach (var dist in districtList)
            {
                dist.targetPopulation = targetPopulation;
            }
        }

        public void MoveShapesToNewDistrict(int targetDistrictID, List<UOG> uogIDList)
        {
            if (districtList[targetDistrictID].isLocked)
            {
                MessageBox.Show("Target District is Locked");
                return;
            }

            if (uogIDList == null)
            {
                // couldn't find any shapes, maybe the lasso got messed up
                // if no uogIDs were found, maybe the user selected a non-florida region
                MessageBox.Show("Could not find any shapes within Florida, please try again.");
            }
            else if (uogIDList.Count == 0)
            {
                // couldn't find any shapes, maybe the lasso got messed up
                // if no uogIDs were found, maybe the user selected a non-florida region
                MessageBox.Show("Could not find any shapes , please try again.");

            }
            else
            {

                MoveUogIDsToDistrict(targetDistrictID, uogIDList, changedDistrictsList);
                RaiseDistrictShapesChanged(new EventArgs());
            }
        }

        public void SetActiveDistrict(int districtID)
        {
            districtSelectedIndex = districtID;
            UpdateSelectedDistrictSubShapeList();  //TODO: After a district button click, SetActiveDistrict calls UpdateSelectedDistrictSubShapeList probably should be done with an event (but I'm not sure).
        }

        public void UpdateDistrictMetaData(DistrictShapeSTO districtShapeSTO)
        {
            int districtID = districtShapeSTO.districtID;

            bool isContiguous = districtShapeSTO.districtSubShapeList.Count <= 1;

            districtList[districtID].lastChangedTimeStamp = DateTime.Now;
            districtList[districtID].boundingBox = districtShapeSTO.districtShapeBoundingBox;
            districtList[districtID].centerPoint = districtShapeSTO.centerPoint;
            districtList[districtID].subShapeMetadataList = DistrictSubShapeMetadata.GetSubShapeMetadata(isContiguous, districtShapeSTO.districtSubShapeList);
            districtList[districtID].subShapeHoleMetadataList = DistrictSubShapeMetadata.GetSubShapeHoleMetadata(districtShapeSTO.districtHolesList);
            districtList[districtID].isContiguous = isContiguous;
            districtList[districtID].hasHole = (districtList[districtID].subShapeHoleMetadataList.Count > 0);
            UpdateSelectedDistrictSubShapeList();
        }

        public void UpdateSelectedDistrictSubShapeList()
        {
            if (selectedDistrictSubShapeList != null)
            {
                selectedDistrictSubShapeList.Clear();
                selectedDistrictHoleList.Clear();
                if ((selectedDistrict >= 0) & (selectedDistrict < districtList.Count()))
                {
                    if (districtList[selectedDistrict].subShapeMetadataList != null)
                    {
                        foreach (var subShape in districtList[selectedDistrict].subShapeMetadataList)
                        {
                            selectedDistrictSubShapeList.Add(subShape);
                        }

                        foreach (var subShape in districtList[selectedDistrict].subShapeHoleMetadataList)
                        {
                            selectedDistrictHoleList.Add(subShape);
                        }



                        // need to force nonContiguous list property notification because it is derived, no actual change signals View to update
                        NotifyPropertyChanged("selectedDistrictSubShapeList");
                        NotifyPropertyChanged("selectedDistrictHoleList");

                    }
                }
            }

        }

        public List<UOG> GetDistrictUogIDs(int maxDistricts, int cutDistrictID)
        {
            List<UOG> uogIDList = new List<UOG>();

            // get the condensed list of UogIDs that make up this district
            List<string> condensedList = censusGeometry.ToCondensedList(maxDistricts);

            // make an assumption that the selected district is under maxDistricts
            foreach (var uogID in condensedList[cutDistrictID].Split(','))
            {
                if (uogID.Length > 0)
                {
                    uogIDList.Add(new UOG(Convert.ToInt32(uogID)));
                }
            }

            return uogIDList;
        }

        public void UndoChanges()
        {
            List<CensusGeometryHistoryChange> changeList = censusGeometry.history.GetChangeList();

            // first get a list of all the districts they used to be in, plus the one they are in now
            changedDistrictsList.Clear();

            // old districts
            int originalDistrictID = 0;  // worst case, we'll put everything to unassigned
            var originalDistrictQuery = (from chng in changeList
                                         select chng.oldDistrictID).Distinct();
            foreach (var oldDistrictID in originalDistrictQuery)
            {
                if (!changedDistrictsList.Contains(oldDistrictID))
                {
                    changedDistrictsList.Add(oldDistrictID);
                    originalDistrictID = oldDistrictID;
                }
            }

            // current districts  (This better be one and only 1 district)
            var currentlDistrictQuery = (from chng in changeList
                                         select chng.newDistrictID).Distinct();
            foreach (var newDistrictID in currentlDistrictQuery)
            {
                if (!changedDistrictsList.Contains(newDistrictID))
                {
                    changedDistrictsList.Add(newDistrictID);
                }
            }

            foreach (var changedDistrictID in changedDistrictsList)
            {
                // then for each district, build its uogIDList
                var uogIDList = from chng in changeList
                                where chng.oldDistrictID == changedDistrictID
                                select chng.uogID;

                // then for each district, put those uogIDs back in the district they used to be
                censusGeometry.SetUogListToDistrict(changedDistrictID, uogIDList.ToList());

            }

            RaiseDistrictShapesChanged(new EventArgs());
        }


        public void MoveUogIDsToDistrict(int targetDistrictID, List<UOG> uogIDList, List<int> changedDistrictsList)
        {
            // build a uogIDList of unlocked elements
            List<UOG> UnlockedUogIDList = new List<UOG>();
            foreach (var uog in uogIDList)
            {
                int assignedDistrict = censusGeometry.GetAssignedDistrict(uog.uogID);
                // assigned district may be a multiDistrictIndicator, and thus out of range
                if ((assignedDistrict < 0) | (assignedDistrict >= districtList.Count))
                {
                    // not assigned to a district
                    UnlockedUogIDList.Add(uog);
                }
                else
                {
                    // assigned to a district
                    if (!districtList[assignedDistrict].isLocked)
                    {
                        // but district isn't locked
                        UnlockedUogIDList.Add(uog);
                    }
                }
            }
            uogIDList = UnlockedUogIDList;


            // we get here with a list of UogIDs to be reassigned to the target district
            // first identify the source regions for the uogID's affected by this district reassignment (in this case a region represents a district)
            List<UOG> districtReassignmentUogIDObservableCollection = censusGeometry.FindRegionsInUogIDList(uogIDList);

            // need to find a distinct list of all source districts this reassignment area may be scattered across
            List<int> sourceDistrictList = censusGeometry.FindDistrictsInUogIDList(uogIDList);

            // need to know which districts where modified
            changedDistrictsList.Clear();
            if (targetDistrictID != Constants.UnassignedDistrictID)
            {
                changedDistrictsList.Add(targetDistrictID);
            }
            foreach (var dist in sourceDistrictList)
            {
                if ((!changedDistrictsList.Contains(dist)) & (dist != Constants.UnassignedDistrictID))
                {
                    changedDistrictsList.Add(dist);
                }
            }

            // special case: easy un-assign if all regions selected already belongs to target district, unassign it.
            int nonTargetDistrictAssignedRegionCount = censusGeometry.CountRegionsInDistrict(targetDistrictID, districtReassignmentUogIDObservableCollection);
            if ((nonTargetDistrictAssignedRegionCount == 0) & (changedDistrictsList.Count > 0))
            {
                if ((changedDistrictsList.Count == 1) & (changedDistrictsList[0] == targetDistrictID))
                {
                    targetDistrictID = Constants.UnassignedDistrictID;
                }
            }


            // now reassign all the selected shapes to the target district
            censusGeometry.SetUogListToDistrict(targetDistrictID, districtReassignmentUogIDObservableCollection);

        }
    }
}
